Stop Watching The VIX, Just Make Your Own

Quant Galore
The Financial Journal
5 min readSep 1, 2022

--

The VIX has been an indispensable tool to traders and a headline-grabber for decades now. So can it be profitable to make the VIX of any stock, not just the S&P 500?

Tech Requirements

This won’t require a complicated setup, all we need is Python and data. Within Python we need to use the pandas package, as we will perform the simple operations on a dataframe.

As for data, we will go through CBOE datashop as the data comes pre-structured. The cost is about $13 per day of intraday options that include all strikes and expirations along with greeks. Once you receive the download, you can get to work right away without any cleaning steps.

Background

Much talk about the VIX has been boiled down to it being a “fear gauge” or some other vague notion of it being a volatility index. Both of these are correct, but let’s get slightly deeper into what the VIX really is.

In sum, the VIX is the market saying “Hey, there’s a 68% probability that I’ll go up or down by this value over the next year. I might be over or under shooting that value, but today, I say there’s a 68% probability of that.”

So, a VIX of $20 represents what the annual volatility estimate of the market is (20% up or down), with a probability of 68%.

The derivation of the VIX takes in a strip of options, but we will use an easier method in our own calculation, so it isn’t imperative we dive into the formulaic nuance of the VIX calculation(Methodology here: https://cdn.cboe.com/api/global/us_indices/governance/Volatility_Index_Methodology_Cboe_Volatility_Index.pdf).

Implementing

You’ll be surprised by how simple this is. All we need to do is track the price of an ATM delta-neutral straddle. So let’s use SPY for example:

  • SPY is currently trading at 410
  • We take the 410 call and the 410 put, the delta of these two combined are close enough to zero to be considered delta-neutral
  • We continuously do this throughout the trading day, changing the straddle as SPY price changes.

But how well does this correlate to the VIX anyway? Well, by quite a bit:

August 4th, ExpirationDates of SPY straddles and VIX as benchmark

The one-month straddle had a correlation to the VIX of 83%!

So now that we have a good way of replicating the VIX, we can apply this to any stock that has an options chain.

Let’s use Apple this time:

This makes sense as AAPL and SPY are quite correlated already, but it adds validity to this model as the correlation of our VIX matches that of the index it is largely represented in.

Final Thoughts

You have just learned how to create the VIX for quite literally any stock with an options chain. There are a plethora of ways you can monetize this, but here we teach how to fish, not give them away, so here are some questions you should consider, the profits lie therein.

  • If you ran this for all constituents of the S&P, but found that some stocks were outliers, can you explain what made its VIX an outlier? Does it have predictive value for its underlying or any others?
  • If you created a custom basket of stocks, say a basket of defense stocks, if you calculate all of their VIX’s, can you make an arbitrage by selling the straddle of the overpriced stock, and buying the straddle of the underpriced? Since the stocks are in the same sector and likely correlated, how would that effect risk/reward?
  • Do this per sector, then intra-sector. Why is the VIX of home furniture stocks much higher than that of home security stocks?

If this whet your appetite, feel free to implement this and write about how it went for you. Take the challenge and answer some of these questions below. Head on over to The Financial Journal for more like this!

Code

import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
def round_to_multiple(number, multiple):
return multiple * round(number / multiple)
SPY_Data= pd.read_csv('path to your data\\SPY 08-04.csv')
AAPL_Data = pd.read_csv('path to your data\\AAPL 08-04.csv')
Unique_SPY_Dates = SPY_Data.drop_duplicates(subset = 'quote_datetime')
Unique_AAPL_Dates = AAPL_Data.drop_duplicates(subset = 'quote_datetime')
SPY_Underlying = Unique_SPY_Dates[['quote_datetime','active_underlying_price']]
AAPL_Underlying = Unique_AAPL_Dates[['quote_datetime','active_underlying_price']]
def Initialize_Universe(SPY_Expiration_Date, AAPL_Expiration_Date, Title):

''' Isolate calls and puts and assign at the money strikes '''
SPY_Option_Data_Expiration = SPY_Data[SPY_Data['expiration'] == SPY_Expiration_Date]

SPY_Calls = SPY_Option_Data_Expiration[SPY_Option_Data_Expiration['option_type'] == 'C']
SPY_Calls['ATM Strike'] = round_to_multiple(SPY_Calls['active_underlying_price'], 1)
SPY_Puts = SPY_Option_Data_Expiration[SPY_Option_Data_Expiration['option_type'] == 'P']
SPY_Puts['ATM Strike'] = round_to_multiple(SPY_Puts['active_underlying_price'], 1)

SPY_Quote_Times = list(Unique_SPY_Dates['quote_datetime'])

Unique_SPY_Calls = pd.DataFrame([])
Unique_SPY_Puts = pd.DataFrame([])

for a in SPY_Quote_Times:
SPY_Call_Universe = SPY_Calls[SPY_Calls['quote_datetime'] == a]
SPY_Call_Leg = SPY_Call_Universe[SPY_Call_Universe['strike'] == SPY_Call_Universe['ATM Strike']]

Unique_SPY_Calls = pd.concat([Unique_SPY_Calls, SPY_Call_Leg])

for b in SPY_Quote_Times:
SPY_Put_Universe = SPY_Puts[SPY_Puts['quote_datetime'] == b]
SPY_Put_Leg = SPY_Put_Universe[SPY_Put_Universe['strike'] == SPY_Put_Universe['ATM Strike']]

Unique_SPY_Puts = pd.concat([Unique_SPY_Puts, SPY_Put_Leg])

Unique_SPY_Calls = Unique_SPY_Calls.add_prefix('SPY C ')
Unique_SPY_Puts = Unique_SPY_Puts.add_prefix('SPY P ')

AAPL_Option_Data_Expiration = AAPL_Data[AAPL_Data['expiration'] == AAPL_Expiration_Date]

AAPL_Calls = AAPL_Option_Data_Expiration[AAPL_Option_Data_Expiration['option_type'] == 'C']
AAPL_Calls['ATM Strike'] = round_to_multiple(AAPL_Calls['active_underlying_price'], 2.5)
AAPL_Puts = AAPL_Option_Data_Expiration[AAPL_Option_Data_Expiration['option_type'] == 'P']
AAPL_Puts['ATM Strike'] = round_to_multiple(AAPL_Puts['active_underlying_price'], 2.5)

AAPL_Quote_Times = list(Unique_AAPL_Dates['quote_datetime'])

Unique_AAPL_Calls = pd.DataFrame([])
Unique_AAPL_Puts = pd.DataFrame([])

for c in AAPL_Quote_Times:
AAPL_Call_Universe = AAPL_Calls[AAPL_Calls['quote_datetime'] == c]
AAPL_Call_Leg = AAPL_Call_Universe[AAPL_Call_Universe['strike'] == AAPL_Call_Universe['ATM Strike']]

Unique_AAPL_Calls = pd.concat([Unique_AAPL_Calls, AAPL_Call_Leg])

for d in AAPL_Quote_Times:
AAPL_Put_Universe = AAPL_Puts[AAPL_Puts['quote_datetime'] == d]
AAPL_Put_Leg = AAPL_Put_Universe[AAPL_Put_Universe['strike'] == AAPL_Put_Universe['ATM Strike']]

Unique_AAPL_Puts = pd.concat([Unique_AAPL_Puts, AAPL_Put_Leg])

Unique_AAPL_Calls = Unique_AAPL_Calls.add_prefix('AAPL C ')
Unique_AAPL_Puts = Unique_AAPL_Puts.add_prefix('AAPL P ')

''' Creates Straddle '''

SPY_Straddle = pd.concat([Unique_SPY_Calls.reset_index(drop=True), Unique_SPY_Puts.reset_index(drop=True)], axis = 1)

SPY_Straddle['SPY Straddle Price'] = ((SPY_Straddle['SPY C ask'] + SPY_Straddle['SPY C bid'] ) / 2 ) + ((SPY_Straddle['SPY P ask'] + SPY_Straddle['SPY P bid'] ) / 2 )
SPY_Straddle['SPY Straddle Returns'] = SPY_Straddle['SPY Straddle Price'].pct_change().fillna(0)
SPY_Straddle['SPY Straddle Cumulative Returns'] = SPY_Straddle['SPY Straddle Returns'].cumsum()

AAPL_Straddle = pd.concat([Unique_AAPL_Calls.reset_index(drop=True), Unique_AAPL_Puts.reset_index(drop=True)], axis = 1)

AAPL_Straddle['AAPL Straddle Price'] = ((AAPL_Straddle['AAPL C ask'] + AAPL_Straddle['AAPL C bid'] ) / 2 ) + ((AAPL_Straddle['AAPL P ask'] + AAPL_Straddle['AAPL P bid'] ) / 2 )
AAPL_Straddle['AAPL Straddle Returns'] = AAPL_Straddle['AAPL Straddle Price'].pct_change().fillna(0)
AAPL_Straddle['AAPL Straddle Cumulative Returns'] = AAPL_Straddle['AAPL Straddle Returns'].cumsum()

SPY_AAPL_Straddle = pd.concat([SPY_Straddle.reset_index(drop = True),AAPL_Straddle.reset_index(drop = True)], axis = 1)
SPY_AAPL_Straddle = SPY_AAPL_Straddle[SPY_AAPL_Straddle['SPY C quote_datetime'].astype(str).isin(SPY_AAPL_Straddle['AAPL C quote_datetime'].astype(str))]

SPY_AAPL_Straddle_Correlation = SPY_AAPL_Straddle[['SPY Straddle Returns','AAPL Straddle Returns']]
SPY_AAPL_Straddle_Correlation = SPY_AAPL_Straddle_Correlation.corr()

Correlation_As_String = 'Correlation: ' + (SPY_AAPL_Straddle_Correlation.iloc[1][0]*100).astype(str) + '%'

plt.plot(pd.to_datetime(SPY_AAPL_Straddle['SPY C quote_datetime']),SPY_AAPL_Straddle['SPY Straddle Cumulative Returns'])
plt.plot(pd.to_datetime(SPY_AAPL_Straddle['SPY C quote_datetime']),SPY_AAPL_Straddle['AAPL Straddle Cumulative Returns'])
plt.title(Title)

plt.suptitle(Correlation_As_String)
plt.legend(['SPY Straddle','AAPL Straddle'])

plt.show()

return SPY_AAPL_Straddle

--

--

Quant Galore
The Financial Journal

Finance, Math, and Code. Why settle for less? @ The Quant's Playbook on Substack